package si.unimb.src.wifisms.entity;

import java.io.Serializable;

/**
 * 
 * @author Ales
 * 
 * Razred AuthToken se generira iz Account.
 * 
 * Je mehanizem za preverjanje in pomnenje gesel, ki se v Account-u ne nujno shranjujejo.
 * Sluzi tudi kot sinhronizacijski mehanizem pri vzporednem posiljanju, saj nekatere niti
 * cakajo na to, da se AuthToken validira.
 * 
 * Implementiran vmesnik Serializable (zaradi serializacije na disk).
 * 
 */
public class AuthToken implements Serializable {

	private static final long serialVersionUID = -7531591950633055230L;
	
	// Tokenu se lahko spreminja le status
	public final String username;
	public final String password;

	// Mozni statusi
	public static final byte STATUS_VALID = 1; // user/pass sta veljavna
	public static final byte STATUS_NOT_CHECKED = 0; // veljavnost se se ni preverjala
	public static final byte STATUS_INVALID = -1; // user/pass sta napacna
	public static final byte STATUS_UNABLE_TO_CHECK = -2; // napaka pri preverjanju statusa

	// Status
	private byte status = STATUS_NOT_CHECKED;
	
	// Veljavno do
	private long validUntil = 0;

	/**
	 * 
	 * Privzeti konstruktor. Instanca se ustvari na podlagi podanega Accounta.
	 * V primer, da Account ne vsebuje gesla, pride do InvalidTokenException.
	 * 
	 * @param account Account, na podlagi katerega se bo generiral AuthToken
	 * @throws InvalidTokenException Ko podani Account nima vnesenega gesla.
	 */
	public AuthToken(Account account) throws InvalidTokenException {

		// Geslo mora biti nastavljeno
		String password = account.getPassword();
		if (password == null) {
		
			throw new InvalidTokenException(account);
			
		}

		// Nastavimo podatke
		this.username = account.getUsername();
		this.password = password;

	}

	/**
	 * 
	 * Getter za status
	 * 
	 * @return Trenutni status AuthTokena
	 */
	public byte getStatus() {

		return status;

	}

	/**
	 * 
	 * Setter za status.
	 * Zaradi moznega dostopa vec nitih hkrati, je metoda synchronized.
	 * 
	 * V primeru da je novi status veljaven, se AuthTokenu podaljsa zivljenska doba.
	 * 
	 * @param status Nov status, ki ga zelimo nastaviti
	 */
	public synchronized void setStatus(byte status) {

		this.status = status;
		
		// Podaljsamo zivljensko dobo
		if(status == STATUS_VALID) {
		
			expandLifetime();
			
		}
		
		// Obvestimo vse niti, ki cakajo da se token preveri
		notifyAll();

	}
	
	/**
	 * 
	 * Za ugotavljanje, ali je potrebna validacija ali ne.
	 * 
	 * @return Ali je potrebna validacija ali ne?
	 */
	public boolean needsValidation() {
		
		switch(status) {
		
			// Ni bilo preverjeno
			case STATUS_NOT_CHECKED:
			case STATUS_UNABLE_TO_CHECK:
				return true;
			
			// Ce je ze veljaven, pogledamo ali mu je potekel lifetime
			case STATUS_VALID:
				return (System.currentTimeMillis() > validUntil);
				
			// Drugace pa ni treba (neveljavnih nima smisla se 1x preverjati)
			default:
				return false;
		
		}
		
	}
	
	/**
	 * 
	 * Podaljsamo zivljensko dobo AuthTokena za X milisekund.
	 * (X = Settings.TOKEN_LIFETIME)
	 * 
	 */
	public void expandLifetime() {
	
		validUntil = System.currentTimeMillis() + Settings.TOKEN_LIFETIME;
		
	}
	
	/**
	 * 
	 * Funkcija za primerjavo dveh tokenov.
	 * 
	 * @param token Drug token, s katerim zelimo primerjati
	 * @return True, ce sta username in password v obeh tokenih enaka. Drugace false.
	 */
	@Override
	public boolean equals(Object token) {

		// Ce primerjamo samega s seboj
		if(this == token) return true;
		
		// Ce ni instanca pravega razreda
		if(!(token instanceof AuthToken)) return false;
		
		// Drugace pa preverimo, ali sta username in password enaka
		AuthToken tokenTmp = (AuthToken)token;

		return
			tokenTmp.username.equals(username) &&
			tokenTmp.password.equals(password);

	}
	
	/**
	 * 
	 * Ta metoda se uporablja, ko zelimo cakati tako dolgo,
	 * dokler se tokenu ne spremeni status.
	 * 
	 */
	public synchronized void waitForValidation() {

		while(status == STATUS_NOT_CHECKED) {
	
			try {
				
				wait();
				
			} catch (InterruptedException e) {}
			
		}
		
	}

}
